/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.webapp.gwt.oba_application.view; import java.util.Date; import org.onebusaway.transit_data.model.tripplanning.ConstraintsBean; import org.onebusaway.transit_data.model.tripplanning.TransitShedConstraintsBean; import org.onebusaway.webapp.gwt.common.control.FlexibleDateParser; import org.onebusaway.webapp.gwt.common.model.ModelListener; import org.onebusaway.webapp.gwt.common.resources.CommonResources; import org.onebusaway.webapp.gwt.common.widgets.DivPanel; import org.onebusaway.webapp.gwt.common.widgets.DivWidget; import org.onebusaway.webapp.gwt.common.widgets.SpanWidget; import org.onebusaway.webapp.gwt.oba_application.control.ConstraintsParameterMapping; import org.onebusaway.webapp.gwt.oba_application.control.OneBusAwayStandardControl; import org.onebusaway.webapp.gwt.oba_application.model.QueryModel; import org.onebusaway.webapp.gwt.oba_application.resources.OneBusAwayCssResource; import org.onebusaway.webapp.gwt.oba_application.resources.OneBusAwayStandardResources; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressHandler; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.event.MapClickHandler; import com.google.gwt.maps.client.geom.LatLng; import com.google.gwt.maps.client.overlay.Marker; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.FormPanel.SubmitEvent; import com.google.gwt.user.client.ui.FormPanel.SubmitHandler; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ResizableDockLayoutPanel; import com.google.gwt.user.client.ui.TextBox; public class SearchWidget extends FlowPanel { private static OneBusAwayCssResource _css = OneBusAwayStandardResources.INSTANCE.getCss(); private static final String USING_LOCATION_TEXTBOX_STYLE = _css.SearchWidgetTextBoxUsingLocation(); private static final int DEFAULT_SEARCH_WINDOW = 60; private static final int DEFAULT_TRIP_DURATION = 20; private static final int DEFAULT_WALKING_DISTANCE = 5280 / 2; private static final int DEFAULT_TRANSFERS = 1; private static DateTimeFormat _dateFormat = DateTimeFormat.getShortDateFormat(); private static DateTimeFormat _timeFormat = DateTimeFormat.getShortTimeFormat(); private QueryModelListener _modelListener = new QueryModelListener(); private OneBusAwayStandardControl _control; private long _time = System.currentTimeMillis(); private TransitShedConstraintsBean _constraints = new TransitShedConstraintsBean(); /***************************************************************************** * ****************************************************************************/ private TextBox _queryTextBox; private TextBox _addressTextBox; private DivPanel _optionsPanel; private TextBox _dateTextBox; private TextBox _timeTextBox; private ListBox _maxTripLengthBox; private ListBox _maxTransfersListBox; private ListBox _maxWalkDistance; /**** * Location Selection ****/ private MapWidget _map; private boolean _usingLocation = false; private LatLng _location = null; private Marker _locationMarker = null; /**** * Options Panel ****/ private boolean _optionsExpanded = false; private Anchor _optionsButton; private ResizableDockLayoutPanel _dockLayoutPanelParent; public SearchWidget() { setDefaultConstraints(); initializeWidget(); refreshWidgets(); } public void setControl(OneBusAwayStandardControl control) { _control = control; } public void setMapWidget(MapWidget map) { _map = map; } public ModelListener<QueryModel> getQueryModelHandler() { return _modelListener; } public void setDockLayoutPanelParent(ResizableDockLayoutPanel dockLayoutPanel) { _dockLayoutPanelParent = dockLayoutPanel; } private void setDefaultConstraints() { _time = System.currentTimeMillis(); _constraints.setMaxInitialWaitTime(DEFAULT_SEARCH_WINDOW * 60); ConstraintsBean c = _constraints.getConstraints(); c.setMaxTransfers(DEFAULT_TRANSFERS); c.setMaxTripDuration(DEFAULT_TRIP_DURATION * 60); c.setMaxWalkingDistance(DEFAULT_WALKING_DISTANCE); } private void initializeWidget() { addStyleName(_css.SearchWidget()); FormPanel form = new FormPanel(); form.setAction("index.html"); add(form); form.addSubmitHandler(new SubmitHandler() { public void onSubmit(SubmitEvent event) { event.cancel(); } }); FlowPanel panel = new FlowPanel(); form.add(panel); DivPanel searchPanel = new DivPanel(); searchPanel.addStyleName(_css.SearchWidgetSearchPanel()); panel.add(searchPanel); DivPanel searchForPanel = new DivPanel(); searchForPanel.addStyleName(_css.SearchWidgetSearchForPanel()); searchPanel.add(searchForPanel); DivWidget queryLabel = new DivWidget("Search for:"); queryLabel.addStyleName(_css.SearchWidgetLabel()); searchForPanel.add(queryLabel); DivPanel queryTextBoxPanel = new DivPanel(); queryTextBoxPanel.addStyleName(_css.SearchWidgetTextBoxPanel()); searchForPanel.add(queryTextBoxPanel); _queryTextBox = new TextBox(); _queryTextBox.addStyleName(_css.SearchWidgetTextBox()); _queryTextBox.setName(ConstraintsParameterMapping.PARAM_QUERY); _queryTextBox.addKeyPressHandler(new QueryTextBoxHandler()); queryTextBoxPanel.add(_queryTextBox); DivPanel searchForExamplePanel = new DivPanel(); searchForExamplePanel.addStyleName(_css.SearchWidgetExamplePanel()); searchForPanel.add(searchForExamplePanel); DivWidget searchForExampleLabel = new DivWidget( "(ex. \"restaurants\", \"parks\", \"grocery stores\")"); searchForExampleLabel.addStyleName(_css.SearchWidgetExampleLabel()); searchForExamplePanel.add(searchForExampleLabel); DivPanel addressPanel = new DivPanel(); searchPanel.add(addressPanel); DivPanel addressPanel1 = new DivPanel(); addressPanel.add(addressPanel1); DivWidget addressLabel = new DivWidget("Start Address:"); addressLabel.addStyleName(_css.SearchWidgetLabel()); addressPanel1.add(addressLabel); DivPanel addressTextBoxPanel = new DivPanel(); addressTextBoxPanel.addStyleName(_css.SearchWidgetTextBoxPanel()); addressPanel1.add(addressTextBoxPanel); _addressTextBox = new TextBox(); _addressTextBox.addStyleName(_css.SearchWidgetTextBox()); _addressTextBox.setName(ConstraintsParameterMapping.PARAM_LOCATION); addressTextBoxPanel.add(_addressTextBox); DivPanel addressPanel2 = new DivPanel(); addressPanel2.addStyleName(_css.SearchWidgetExamplePanel()); addressPanel.add(addressPanel2); SpanWidget addressExampleLabel1 = new SpanWidget( "(ex. \"3rd and pike\" or "); addressExampleLabel1.addStyleName(_css.SearchWidgetExampleLabel()); addressPanel2.add(addressExampleLabel1); Anchor addressExampleLabel2 = new Anchor("use the map"); addressExampleLabel2.addStyleName(_css.SearchWidgetExampleLabel()); addressExampleLabel2.addClickHandler(new UseTheMapHandler()); addressPanel2.add(addressExampleLabel2); SpanWidget addressExampleLabel3 = new SpanWidget(")"); addressExampleLabel3.addStyleName(_css.SearchWidgetExampleLabel()); addressPanel2.add(addressExampleLabel3); DivPanel buttonPanel = new DivPanel(); buttonPanel.addStyleName(_css.SearchWidgetButtonPanel()); searchPanel.add(buttonPanel); Button button = new Button("Go"); buttonPanel.add(button); button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent widget) { handleQuery(); } }); AddressTextBoxHandler handler = new AddressTextBoxHandler(); _addressTextBox.addKeyPressHandler(handler); _addressTextBox.addFocusHandler(handler); _addressTextBox.addBlurHandler(handler); _optionsButton = new Anchor("Show More Options"); _optionsButton.addStyleName(_css.SearchWidgetShowOptionsButton()); _optionsButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent arg0) { toggleExpansion(); // TODO : Refresh layout } }); buttonPanel.add(_optionsButton); DivPanel clearPanel = new DivPanel(); clearPanel.addStyleName(_css.ClearPanel()); panel.add(clearPanel); Image hiddenPixel = new Image( CommonResources.INSTANCE.getHiddenPixel().getUrl()); clearPanel.add(hiddenPixel); _optionsPanel = new DivPanel(); _optionsPanel.addStyleName(_css.SearchWidgetOptionsPanel()); _optionsPanel.setVisible(false); panel.add(_optionsPanel); Grid optionsGrid = new Grid(2, 4); optionsGrid.addStyleName(_css.SearchWidgetOptionsGrid()); for (int i = 0; i < 4; i++) { optionsGrid.getCellFormatter().addStyleName(0, i, "SearchWidget-OptionsGrid-Column" + i); optionsGrid.getCellFormatter().addStyleName(1, i, "SearchWidget-OptionsGrid-Column" + i); } _optionsPanel.add(optionsGrid); SpanWidget timeLabel = new SpanWidget("Start Time:"); optionsGrid.setWidget(0, 0, timeLabel); DivPanel dateAndTimePanel = new DivPanel(); optionsGrid.setWidget(0, 1, dateAndTimePanel); _dateTextBox = new TextBox(); _dateTextBox.addStyleName(_css.SearchWidgetStartDateTextBox()); dateAndTimePanel.add(_dateTextBox); _timeTextBox = new TextBox(); _timeTextBox.addStyleName(_css.SearchWidgetStartTimeTextBox()); dateAndTimePanel.add(_timeTextBox); SpanWidget maxLengthLabel = new SpanWidget("Trip Time:"); optionsGrid.setWidget(1, 0, maxLengthLabel); _maxTripLengthBox = new ListBox(); _maxTripLengthBox.addItem("10 mins", "10"); _maxTripLengthBox.addItem("15 mins", "15"); _maxTripLengthBox.addItem("20 mins", "20"); _maxTripLengthBox.addItem("30 mins", "30"); _maxTripLengthBox.addItem("45 mins", "45"); _maxTripLengthBox.addItem("1 hour", "60"); _maxTripLengthBox.addStyleName(_css.SearchWidgetTripLengthList()); optionsGrid.setWidget(1, 1, _maxTripLengthBox); SpanWidget maxTransfersLabel = new SpanWidget("Transfers:"); optionsGrid.setWidget(0, 2, maxTransfersLabel); _maxTransfersListBox = new ListBox(); _maxTransfersListBox.addItem("Don't Care", "-1"); _maxTransfersListBox.addItem("0", "0"); _maxTransfersListBox.addItem("1", "1"); _maxTransfersListBox.addItem("2", "2"); optionsGrid.setWidget(0, 3, _maxTransfersListBox); SpanWidget maxWalkLabel = new SpanWidget("Walk at most:"); optionsGrid.setWidget(1, 2, maxWalkLabel); _maxWalkDistance = new ListBox(); _maxWalkDistance.addItem("1/4 mile", "1320"); _maxWalkDistance.addItem("1/2 mile", "2640"); _maxWalkDistance.addItem("3/4 mile", "3960"); _maxWalkDistance.addItem("1 mile", "5280"); optionsGrid.setWidget(1, 3, _maxWalkDistance); DivPanel optionsPanelRowB = new DivPanel(); _optionsPanel.add(optionsPanelRowB); } private void refreshWidgets() { Date time = new Date(_time); _dateTextBox.setText(_dateFormat.format(time)); _timeTextBox.setText(_timeFormat.format(time)); ConstraintsBean c = _constraints.getConstraints(); setClosestIndex(_maxTransfersListBox, c.getMaxTransfers()); setClosestIndex(_maxTripLengthBox, c.getMaxTripDuration() / 60); setClosestIndex(_maxWalkDistance, (int) c.getMaxWalkingDistance()); } private void setClosestIndex(ListBox listBox, int value) { int minIndex = -1; double minDiff = 0; for (int i = 0; i < listBox.getItemCount(); i++) { Integer itemValue = Integer.parseInt(listBox.getValue(i)); double diff = Math.abs(value - itemValue); if (minIndex == -1 || diff < minDiff) { minIndex = i; minDiff = diff; } } if (minIndex != -1) listBox.setSelectedIndex(minIndex); } private void refreshConstraints() { Date date = new Date(); String dateValue = _dateTextBox.getText(); String timeValue = _timeTextBox.getText(); if (dateValue.length() > 0 && timeValue.length() > 0) { try { Date parsedDate = _dateFormat.parse(dateValue); FlexibleDateParser parser = new FlexibleDateParser(); int minutes = parser.getMintuesSinceMidnight(timeValue); date = new Date(parsedDate.getTime() + minutes * 60 * 1000); } catch (FlexibleDateParser.DateParseException ex) { System.err.println("bad time=" + dateValue + " " + timeValue); } } System.out.println("date=" + date); _time = date.getTime(); ConstraintsBean c = _constraints.getConstraints(); int maxTransfersIndex = _maxTransfersListBox.getSelectedIndex(); if (maxTransfersIndex != -1) c.setMaxTransfers(Integer.parseInt(_maxTransfersListBox.getValue(maxTransfersIndex))); int maxTripDurationIndex = _maxTripLengthBox.getSelectedIndex(); if (maxTripDurationIndex != -1) c.setMaxTripDuration(Integer.parseInt(_maxTripLengthBox.getValue(maxTripDurationIndex)) * 60); int maxWalkDistanceindex = _maxWalkDistance.getSelectedIndex(); if (maxWalkDistanceindex != -1) c.setMaxWalkingDistance(Integer.parseInt(_maxWalkDistance.getValue(maxWalkDistanceindex))); } private void toggleExpansion() { _optionsExpanded = !_optionsExpanded; _optionsPanel.setVisible(_optionsExpanded); _optionsButton.setText(_optionsExpanded ? "Hide Options" : "Show More Options"); double size = _optionsExpanded ? 8 : 4; _dockLayoutPanelParent.setWidgetSize(this, size); _dockLayoutPanelParent.forceLayout(); } private void handleQuery() { String queryText = _queryTextBox.getText(); String addressText = _addressTextBox.getText(); if (queryText == null || queryText.length() == 0) return; if (!_usingLocation && (addressText == null || addressText.length() == 0)) return; refreshConstraints(); if (_usingLocation) addressText = ""; _control.query(queryText, addressText, _location, _time, _constraints); } private void setLocation(LatLng location) { _addressTextBox.setText("See the marker on the map..."); _addressTextBox.addStyleName(USING_LOCATION_TEXTBOX_STYLE); _location = location; if (_locationMarker != null) { _map.removeOverlay(_locationMarker); _locationMarker = null; } _locationMarker = new Marker(_location); _map.addOverlay(_locationMarker); _map.setCenter(_location); _usingLocation = true; } /***************************************************************************** * Inner Classes ****************************************************************************/ private class QueryTextBoxHandler implements KeyPressHandler { public void onKeyPress(KeyPressEvent event) { if (event.getCharCode() == '\n' && !event.isAnyModifierKeyDown()) { handleQuery(); } } } private class AddressTextBoxHandler implements KeyPressHandler, FocusHandler, BlurHandler { public void onKeyPress(KeyPressEvent event) { if (event.getCharCode() == '\n' && !event.isAnyModifierKeyDown()) { handleQuery(); } else { _usingLocation = false; _location = null; if (_locationMarker != null) _map.removeOverlay(_locationMarker); } } public void onFocus(FocusEvent event) { if (_usingLocation) { _addressTextBox.removeStyleName(USING_LOCATION_TEXTBOX_STYLE); _addressTextBox.setText(""); } } public void onBlur(BlurEvent arg0) { if (_usingLocation) { _addressTextBox.setText("See the marker on the map..."); _addressTextBox.addStyleName(USING_LOCATION_TEXTBOX_STYLE); } } } private class QueryModelListener implements ModelListener<QueryModel> { public void handleUpdate(QueryModel model) { _queryTextBox.setText(model.getQuery()); _constraints = new TransitShedConstraintsBean(model.getConstraints()); String locationValue = model.getLocationQuery(); if ((locationValue == null || locationValue.length() == 0) && model.getLocation() != null) { setLocation(model.getLocation()); } else { _addressTextBox.setText(locationValue); } refreshWidgets(); } } private class UseTheMapHandler implements ClickHandler, MapClickHandler { public void onClick(ClickEvent widget) { _addressTextBox.setText("Click on the map to pick a location..."); _addressTextBox.addStyleName(USING_LOCATION_TEXTBOX_STYLE); _map.addMapClickHandler(this); } public void onClick(MapClickEvent event) { _map.removeMapClickHandler(this); setLocation(event.getLatLng()); } } }